<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
	<title>HTML5 Canvas Dream Tree</title>
	<script src="https://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
	<script type="text/javascript">
		(function ($) {
			var Vector = function (x, y) {
				this.x = x || 0;
				this.y = y || 0;
			};
			Vector.prototype = {
				add: function (v) {
					this.x += v.x;
					this.y += v.y;
					return this;
				},
				length: function () {
					return Math.sqrt(this.x * this.x + this.y * this.y);
				},
				rotate: function (theta) {
					var x = this.x;
					var y = this.y;
					this.x = Math.cos(theta) * this.x - Math.sin(theta) * this.y;
					this.y = Math.sin(theta) * this.x + Math.cos(theta) * this.y;
					//this.x = Math.cos(theta) * x - Math.sin(theta) * y;
					//this.y = Math.sin(theta) * x + Math.cos(theta) * y;
					return this;
				},
				mult: function (f) {
					this.x *= f;
					this.y *= f;
					return this;
				}
			};
			var Leaf = function (p, r, c, ctx) {
				this.p = p || null;
				this.r = r || 0;
				this.c = c || 'rgba(255,255,255,1.0)';
				this.ctx = ctx;
			}
			Leaf.prototype = {
				render: function () {
					var that = this;
					var ctx = this.ctx;
					var f = Branch.random(1, 2)
					for (var i = 0; i < 5; i++) {
						(function (r) {
							setTimeout(function () {
								ctx.beginPath();
								ctx.fillStyle = that.color;
								ctx.moveTo(that.p.x, that.p.y);
								ctx.arc(that.p.x, that.p.y, r, 0, Branch.circle, true);
								ctx.fill();
							}, r * 60);
						})(i);
					}
				}
			}
			var Branch = function (p, v, r, c, t) {
				this.p = p || null;
				this.v = v || null;
				this.r = r || 0;
				this.length = 0;
				this.generation = 1;
				this.tree = t || null;
				this.color = c || 'rgba(255,255,255,1.0)';
				this.register();
			};
			Branch.prototype = {
				register: function () {
					this.tree.addBranch(this);
				},
				draw: function () {
					var ctx = this.tree.ctx;
					ctx.beginPath();
					ctx.fillStyle = this.color;
					ctx.moveTo(this.p.x, this.p.y);
					ctx.arc(this.p.x, this.p.y, this.r, 0, Branch.circle, true);
					ctx.fill();
				},
				modify: function () {
					var angle = 0.18 - (0.10 / this.generation);
					this.p.add(this.v);
					this.length += this.v.length();
					this.r *= 0.99;
					this.v.rotate(Branch.random(-angle, angle)); //.mult(0.996);
					if (this.r < 0.8 || this.generation > 10) {
						this.tree.removeBranch(this);
						var l = new Leaf(this.p, 10, this.color, this.tree.ctx);
						l.render();
					}
				},
				grow: function () {
					this.draw();
					this.modify();
					this.fork();
				},
				fork: function () {
					var p = this.length - Branch.random(100, 200); // + (this.generation * 10);
					if (p > 0) {
						var n = Math.round(Branch.random(1, 3));
						this.tree.stat.fork += n - 1;
						for (var i = 0; i < n; i++) {
							Branch.clone(this);
						}
						this.tree.removeBranch(this);
					}
				}
			};
			Branch.circle = 2 * Math.PI;
			Branch.random = function (min, max) {
				return Math.random() * (max - min) + min;
			};
			Branch.clone = function (b) {
				var r = new Branch(new Vector(b.p.x, b.p.y), new Vector(b.v.x, b.v.y), b.r, b.color, b.tree);
				r.generation = b.generation + 1;
				return r;
			};
			Branch.rgba = function (r, g, b, a) {
				return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
			};
			Branch.randomrgba = function (min, max, a) {
				return Branch.rgba(Math.round(Branch.random(min, max)), Math.round(Branch.random(min, max)), Math.round(Branch.random(min, max)), a);
			};
			var Tree = function () {
				var branches = [];
				var timer;
				this.stat = {
					fork: 0,
					length: 0
				};
				this.addBranch = function (b) {
					branches.push(b);
				};
				this.removeBranch = function (b) {
					for (var i = 0; i < branches.length; i++) {
						if (branches[i] === b) {
							branches.splice(i, 1);
							return;
						}
					}
				};
				this.render = function (fn) {
					var that = this;
					timer = setInterval(function () {
						fn.apply(that, arguments);
						if (branches.length > 0) {
							for (var i = 0; i < branches.length; i++) {
								branches[i].grow();
							}
						} else {
							//clearInterval(timer);
						}
					}, 1000 / 30);
				};
				this.init = function (ctx) {
					this.ctx = ctx;
				};
				this.abort = function () {
					branches = [];
					this.stat = {
						fork: 0,
						length: 0
					}
				};
			};
			function init() {
				// init
				var $window = $(window);
				var $body = $("body");
				var canvas_width = $window.width();
				var canvas_height = $window.height() - 30;
				var center_x = canvas_width / 2;
				var stretch_factor = 600 / canvas_height;
				var y_speed = 3 / stretch_factor;
				var $statMsg = $("#statMsg");
				// tx
				var canvas = $('#canvas')[0];
				canvas.width = canvas_width;
				canvas.height = canvas_height;
				var ctx = canvas.getContext("2d");
				ctx.globalCompositeOperation = "lighter";
				// tree
				var t = new Tree();
				t.init(ctx);
				for (var i = 0; i < 3; i++) {
					new Branch(new Vector(center_x, canvas_height), new Vector(Math.random(-1, 1), -y_speed), 15 / stretch_factor, Branch.randomrgba(0, 255, 0.3), t);
				}
				t.render(function () {
					$statMsg.html(this.stat.fork);
				});
			}
			$(function () {
				init();
			});
		})(jQuery);
	</script>
	<style type="text/css">
		body {
			padding: 0;
			margin: 0;
			background: #fff;
			font-family: Courier;
		}
		canvas {
			background-color: #000;
			cursor: pointer;
		}
	</style>
</head>
<body>
<canvas id='canvas'></canvas>
</body>
</html>
